﻿/*
 * Copyright 2009 - 2010 eScience Institute
 *
 * This file is part of SqlShare.
 *
 * SqlShare is free software: you can redistribute it and/or modify it under the terms 
 * of the GNU General Public License as published by the Free Software Foundation, 
 * either version 3 of the License, or (at your option) any later version.
 *
 * SqlShare is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with 
 * SqlShare. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Hosting;
using System.IO;
using RawData;
using System.Xml.Serialization;
using System.Xml;
using System.Data.SqlClient;
using TestSL3;
using SqlShareAPI;

namespace WebQueryRole
{
    /// <summary>
    /// Summary description for FileAnalyzer
    /// </summary>
    public class FileAnalyzer : IHttpHandler
    {
        // we need to maintain a session between these two sessions        
        static Dictionary<string, DataUploadPersist> _clzPersistStorage = new Dictionary<string, DataUploadPersist>();

        private HttpContext _context;
        private ResponseMessage _response;
        public void ProcessRequest(HttpContext context)
        {
            string key = null;
            try
            {             
                _context = context;
                _response = new ResponseMessage();

                key = _context.Request["filename"];
                string mode = _context.Request["mode"];

                // check param constraints
                if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(mode))
                {
                    throw new Exception("Invalid required parameters");
                }

                // check existing persists
                if (!_clzPersistStorage.ContainsKey(key))
                {
                    if (!mode.Equals("initialize"))
                    {
                        throw new Exception("Unknown key entering non-first stage: " + key);
                    }                    
                }
                
                // find proper method to execute
                if (mode.Equals("initialize"))
                {
                    ProcessNewParams(key);
                }
                else if (mode.Equals("upload"))
                {
                    ProcessUploadFile(_clzPersistStorage[key]);
                }
                else if (mode.Equals("analyze"))
                {
                    ProcessAnalyzeFile(_clzPersistStorage[key]);
                }
                else if (mode.Equals("bulkinsert"))
                {
                    ProcessBulkInsert(_clzPersistStorage[key]);
                }

                // update the work % items and next mode step
                _clzPersistStorage[key].UpdateWorkParams(_response);

                // clear _clz if needed
                if (_clzPersistStorage[key].CurrentMode == DataUploadPersist.Mode.Complete)
                {
                    _clzPersistStorage[key].Dispose();
                    _clzPersistStorage.Remove(key);
                }
            }
            catch (Exception e)
            {
                _response.Error = e.Message + Environment.NewLine + e.StackTrace;
                // clear memory on error
                if (key != null && _clzPersistStorage.ContainsKey(key))
                {
                    _clzPersistStorage[key].Dispose();
                    _clzPersistStorage.Remove(key);
                }
            }

            WriteOut();
        }


        public void ProcessNewParams(string key)
        {            
            string delStr = _context.Request["delimiter"];
            string hasColumnsStr = _context.Request["hasHeader"];
            string tablename = _context.Request["tablename"];
            
            char delimiter = (delStr.Equals("tab")? '\t' : delStr.ToCharArray()[0]);
            bool hasHeaders = bool.Parse(hasColumnsStr);         

            DataUploadPersist persist = new DataUploadPersist(_context, key, tablename, hasHeaders, delimiter);
            _clzPersistStorage[key] = persist;
        }

        private void ProcessUploadFile(DataUploadPersist persist)
        {
            if (_context.Request.InputStream.Length == 0)
                throw new ArgumentException("No file input");

            bool lastChunk = string.IsNullOrEmpty(_context.Request.QueryString["last"]) ? true : bool.Parse(_context.Request.QueryString["last"]);
            bool firstChunk = string.IsNullOrEmpty(_context.Request.QueryString["first"]) ? true : bool.Parse(_context.Request.QueryString["first"]);
            long startByte = string.IsNullOrEmpty(_context.Request.QueryString["offset"]) ? 0 : long.Parse(_context.Request.QueryString["offset"]); ;

            SaveFile(_context.Request.InputStream, persist.LocalStream);

            if (lastChunk)
            {                
                // bump to next stage
                persist.UpgradeMode();
            }
        }        

        /// <summary>
        /// Process the request for analyzing the file
        /// </summary>
        private void ProcessAnalyzeFile(DataUploadPersist persist)
        {

                // if still in process
                double work = persist.DataAnalyzer.PercentComplete;
                if (work == -1)
                {
                    throw persist.DataAnalyzer.AsyncException;
                }

                // append finished calculated data
                if (work >= 1)
                {
                    persist.UpgradeMode();
                }
            
        }
        

        /// <summary>
        /// Process the request for bulk inserting the file
        /// </summary>
        private void ProcessBulkInsert(DataUploadPersist persist)
        {          
            
            // if still in process
            double work = persist.BatchUploader.PercentComplete;
            if (work == -1)
            {
                throw persist.BatchUploader.AsyncException;
            }
            
            // append finished calculated data
            if (work >= 1)
            {                
                // work is done create table
                _response.Params["tablename"] = persist.BatchUploader.TableName;
                _response.Params["rows"] = persist.BatchUploader.RowsUploaded;

                // this is our last time accessing this table, lets clear the entry to free memory                
                persist.UpgradeMode();
            }            
        }        

        /// <summary>
        /// Run a simple nonquery SQL command and close the db connection
        /// </summary>
        /// <param name="sql"></param>
        private void ExecuteSql(string sql)
        {
            SSAPIFactory factory = CloudUtil.CreateSSAPIFactory();
            UserSession session = WebAuthentication.IsLoggedIn(_context, factory, false);
            if (session == null)
            {
                throw new Exception("Unable to login user to execute SQL");
            }
            try
            {

                SqlCommand cmd = new SqlCommand(sql, session.ContentSqlConnection);
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                _response.Error = e.Message;
            }
            finally
            {
                factory.Close();
            }
        }

        /// <summary>
        /// Write out the message to client, this should be the last thing that is done
        /// </summary>
        private void WriteOut()
        {
            _context.Response.ContentType = "text/xml";            
            _response.WriteToStream(_context.Response.OutputStream);
        }

        private void SaveFile(Stream stream, MemoryStream ms)
        {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
            {
                ms.Write(buffer, 0, bytesRead);
            }
        }               

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Todo: make this a more delgate class for handling everything
        /// </summary>
        class DataUploadPersist
        {
            public MemoryStream LocalStream { get; set; }
            public DataFileReader DataAnalyzer { get; set; }
            public SqlBatchFileUploader BatchUploader { get; set; }
            public Mode CurrentMode;            
            public string Key { get; set; }

            private UserSession _session;

            public DataUploadPersist(HttpContext context, string key, string tablename, bool hasHeaders, char delimiter)
            {
                Key = key;
                CurrentMode = Mode.Upload;
                LocalStream = new MemoryStream();
                DataAnalyzer = new DataFileReader(tablename, delimiter, hasHeaders);
                BatchUploader = new SqlBatchFileUploader(DataAnalyzer.SQLTable, 4000, hasHeaders);

                SSAPIFactory factory = CloudUtil.CreateSSAPIFactory();
                _session = WebAuthentication.IsLoggedIn(context, factory, false);
                if (_session == null)
                {
                    Dispose();
                    throw new Exception("Error: User is not logged in");
                }

                // test table to see if it exists first
                if (BatchUploader.TableExist(_session.ContentSqlConnection)) 
                {
                    Dispose();
                    throw new Exception("Table Already exists, please choose another Table name");
                }   
            }

            public enum Mode {
                Upload, Analyze, BatchUpload, Complete
            }

            /// <summary>
            /// Upgrade the mode we are persisting for this execution process
            /// </summary>
            public void UpgradeMode()
            {
                switch (CurrentMode)
                {
                    case Mode.Analyze:
                        CurrentMode = Mode.BatchUpload;                                                
                        BatchUploader.BulkCopyFile(LocalStream, _session.ContentSqlConnection, true);
                        break;
                    case Mode.BatchUpload:
                        CurrentMode = Mode.Complete;
                        _session.CreateTable(DataAnalyzer.SQLTable.TableName, "", false, false);
                        break;

                    case Mode.Upload:
                        CurrentMode = Mode.Analyze;
                        DataAnalyzer.ExtractMetadata(LocalStream, true);
                        break;
                }
            }

            public void UpdateWorkParams(ResponseMessage msg)
            {
                msg.Params["work2"] = DataAnalyzer == null ? 0 : DataAnalyzer.PercentComplete;
                msg.Params["work3"] = BatchUploader == null ? 0 : BatchUploader.PercentComplete;

                switch (CurrentMode)
                {
                    case Mode.Analyze:
                        msg.Params["mode"] = "analyze";
                        break;

                    case Mode.BatchUpload:
                        msg.Params["mode"] = "bulkinsert";
                        break;

                    case Mode.Upload:
                        msg.Params["mode"] = "upload";
                        break;

                    case Mode.Complete:
                        msg.Params["mode"] = "complete";
                        break;
                }
            }

            public void Dispose()
            {
                // release memory
                if (LocalStream != null)
                {
                    LocalStream.Close();
                }
                DataAnalyzer = null;
                BatchUploader = null;
                if (_session != null)
                {
                    _session.Close();
                }
            }
        }
    }
}